package com.zhjw.aspect;

import com.zhjw.annotation.DS;
import com.zhjw.config.db.DataSourceContextHolderWrapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * Created by zhjw on 2023/3/6
 *
 * 实现@DS注解切面
 *
 * <p>
 * 提升该切面实例化的优先级是为了不影响@Transactional注解生效：
 * 不指定优先级的话{@link AbstractRoutingDataSource#determineTargetDataSource()}方法执行在本切面之前
 * 在{@link AbstractRoutingDataSource#determineTargetDataSource()}方法的194行
 * 会导致该事务一直使用默认的datasource，而不会切换
 * </p>
 *
 * @author zhjw
 * @date 2023/3/6
 */
@Component
@Aspect
@Slf4j
@Order(10)
public class DynamicDataSourceAspect {

    /**
     * 加载数据源的切入点
     */
    @Pointcut("@annotation(com.zhjw.annotation.DS) || @within(com.zhjw.annotation.DS)")
    public void pointCut() {
    }

    /**
     * 加载当前线程使用数据源
     *
     * @param point
     */
    @Before("pointCut()")
    public void beforeSwitchDS(JoinPoint point) {
        //获得当前访问的class
        Class<?> clazz = point.getTarget().getClass();
        //获得访问的方法名
        String methodName = point.getSignature().getName();
        //得到方法的参数的类型
        Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();


        String dataSource = DataSourceContextHolderWrapper.DEFAULT_DS;
        try {
            // 得到访问的方法对象
            Method method = clazz.getMethod(methodName, argClass);

            // 就近原则:方法上注解大于类上注解
            /** 判断类上是否存在@DS注解 **/
            if (clazz.isAnnotationPresent(DS.class)) {
                DS annotation = clazz.getAnnotation(DS.class);
                // 取出注解中的数据源名
                dataSource = annotation.value();
            }
            /**判断方法上是否存在@DS注解 **/
            if (method.isAnnotationPresent(DS.class)) {
                DS annotation = method.getAnnotation(DS.class);
                // 取出注解中的数据源名
                dataSource = annotation.value();
            }
        } catch (Exception e) {
            log.error("@DS AOP failed to dynamic data source ,cause by :{}", e);
        }

        // 切换数据源
        DataSourceContextHolderWrapper.setDB(dataSource);
    }

    /**
     * 清除当前线程使用数据源
     *
     * @param point
     */
    @After("pointCut()")
    public void afterSwitchDS(JoinPoint point) {
        DataSourceContextHolderWrapper.clearDB();
    }

}
